/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.imageio;

import java.awt.Dimension;
import java.util.Locale;

/**
 * A class describing how a stream is to be encoded.  Instances of
 * this class or its subclasses are used to supply prescriptive
 * "how-to" information to instances of <code>ImageWriter</code>.
 *
 * <p> A plug-in for a specific image format may define a subclass of
 * this class, and return objects of that class from the
 * <code>getDefaultWriteParam</code> method of its
 * <code>ImageWriter</code> implementation.  For example, the built-in
 * JPEG writer plug-in will return instances of
 * <code>javax.imageio.plugins.jpeg.JPEGImageWriteParam</code>.
 *
 * <p> The region of the image to be written is determined by first
 * intersecting the actual bounds of the image with the rectangle
 * specified by <code>IIOParam.setSourceRegion</code>, if any.  If the
 * resulting rectangle has a width or height of zero, the writer will
 * throw an <code>IIOException</code>. If the intersection is
 * non-empty, writing will commence with the first subsampled pixel
 * and include additional pixels within the intersected bounds
 * according to the horizontal and vertical subsampling factors
 * specified by {@link IIOParam#setSourceSubsampling
 * IIOParam.setSourceSubsampling}.
 *
 * <p> Individual features such as tiling, progressive encoding, and
 * compression may be set in one of four modes.
 * <code>MODE_DISABLED</code> disables the features;
 * <code>MODE_DEFAULT</code> enables the feature with
 * writer-controlled parameter values; <code>MODE_EXPLICIT</code>
 * enables the feature and allows the use of a <code>set</code> method
 * to provide additional parameters; and
 * <code>MODE_COPY_FROM_METADATA</code> copies relevant parameter
 * values from the stream and image metadata objects passed to the
 * writer.  The default for all features is
 * <code>MODE_COPY_FROM_METADATA</code>.  Non-standard features
 * supplied in subclasses are encouraged, but not required to use a
 * similar scheme.
 *
 * <p> Plug-in writers may extend the functionality of
 * <code>ImageWriteParam</code> by providing a subclass that implements
 * additional, plug-in specific interfaces.  It is up to the plug-in
 * to document what interfaces are available and how they are to be
 * used.  Writers will silently ignore any extended features of an
 * <code>ImageWriteParam</code> subclass of which they are not aware.
 * Also, they may ignore any optional features that they normally
 * disable when creating their own <code>ImageWriteParam</code>
 * instances via <code>getDefaultWriteParam</code>.
 *
 * <p> Note that unless a query method exists for a capability, it must
 * be supported by all <code>ImageWriter</code> implementations
 * (<i>e.g.</i> progressive encoding is optional, but subsampling must be
 * supported).
 *
 * @see ImageReadParam
 */
public class ImageWriteParam extends IIOParam {

  /**
   * A constant value that may be passed into methods such as
   * <code>setTilingMode</code>, <code>setProgressiveMode</code>,
   * and <code>setCompressionMode</code> to disable a feature for
   * future writes.  That is, when this mode is set the stream will
   * <b>not</b> be tiled, progressive, or compressed, and the
   * relevant accessor methods will throw an
   * <code>IllegalStateException</code>.
   *
   * @see #MODE_EXPLICIT
   * @see #MODE_COPY_FROM_METADATA
   * @see #MODE_DEFAULT
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   * @see #setTilingMode
   * @see #getTilingMode
   * @see #setCompressionMode
   * @see #getCompressionMode
   */
  public static final int MODE_DISABLED = 0;

  /**
   * A constant value that may be passed into methods such as
   * <code>setTilingMode</code>,
   * <code>setProgressiveMode</code>, and
   * <code>setCompressionMode</code> to enable that feature for
   * future writes.  That is, when this mode is enabled the stream
   * will be tiled, progressive, or compressed according to a
   * sensible default chosen internally by the writer in a plug-in
   * dependent way, and the relevant accessor methods will
   * throw an <code>IllegalStateException</code>.
   *
   * @see #MODE_DISABLED
   * @see #MODE_EXPLICIT
   * @see #MODE_COPY_FROM_METADATA
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   * @see #setTilingMode
   * @see #getTilingMode
   * @see #setCompressionMode
   * @see #getCompressionMode
   */
  public static final int MODE_DEFAULT = 1;

  /**
   * A constant value that may be passed into methods such as
   * <code>setTilingMode</code> or <code>setCompressionMode</code>
   * to enable a feature for future writes. That is, when this mode
   * is set the stream will be tiled or compressed according to
   * additional information supplied to the corresponding
   * <code>set</code> methods in this class and retrievable from the
   * corresponding <code>get</code> methods.  Note that this mode is
   * not supported for progressive output.
   *
   * @see #MODE_DISABLED
   * @see #MODE_COPY_FROM_METADATA
   * @see #MODE_DEFAULT
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   * @see #setTilingMode
   * @see #getTilingMode
   * @see #setCompressionMode
   * @see #getCompressionMode
   */
  public static final int MODE_EXPLICIT = 2;

  /**
   * A constant value that may be passed into methods such as
   * <code>setTilingMode</code>, <code>setProgressiveMode</code>, or
   * <code>setCompressionMode</code> to enable that feature for
   * future writes.  That is, when this mode is enabled the stream
   * will be tiled, progressive, or compressed based on the contents
   * of stream and/or image metadata passed into the write
   * operation, and any relevant accessor methods will throw an
   * <code>IllegalStateException</code>.
   *
   * <p> This is the default mode for all features, so that a read
   * including metadata followed by a write including metadata will
   * preserve as much information as possible.
   *
   * @see #MODE_DISABLED
   * @see #MODE_EXPLICIT
   * @see #MODE_DEFAULT
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   * @see #setTilingMode
   * @see #getTilingMode
   * @see #setCompressionMode
   * @see #getCompressionMode
   */
  public static final int MODE_COPY_FROM_METADATA = 3;

  // If more modes are added, this should be updated.
  private static final int MAX_MODE = MODE_COPY_FROM_METADATA;

  /**
   * A <code>boolean</code> that is <code>true</code> if this
   * <code>ImageWriteParam</code> allows tile width and tile height
   * parameters to be set.  By default, the value is
   * <code>false</code>.  Subclasses must set the value manually.
   *
   * <p> Subclasses that do not support writing tiles should ensure
   * that this value is set to <code>false</code>.
   */
  protected boolean canWriteTiles = false;

  /**
   * The mode controlling tiling settings, which Must be
   * set to one of the four <code>MODE_*</code> values.  The default
   * is <code>MODE_COPY_FROM_METADATA</code>.
   *
   * <p> Subclasses that do not writing tiles may ignore this value.
   *
   * @see #MODE_DISABLED
   * @see #MODE_EXPLICIT
   * @see #MODE_COPY_FROM_METADATA
   * @see #MODE_DEFAULT
   * @see #setTilingMode
   * @see #getTilingMode
   */
  protected int tilingMode = MODE_COPY_FROM_METADATA;

  /**
   * An array of preferred tile size range pairs.  The default value
   * is <code>null</code>, which indicates that there are no
   * preferred sizes.  If the value is non-<code>null</code>, it
   * must have an even length of at least two.
   *
   * <p> Subclasses that do not support writing tiles may ignore
   * this value.
   *
   * @see #getPreferredTileSizes
   */
  protected Dimension[] preferredTileSizes = null;

  /**
   * A <code>boolean</code> that is <code>true</code> if tiling
   * parameters have been specified.
   *
   * <p> Subclasses that do not support writing tiles may ignore
   * this value.
   */
  protected boolean tilingSet = false;

  /**
   * The width of each tile if tiling has been set, or 0 otherwise.
   *
   * <p> Subclasses that do not support tiling may ignore this
   * value.
   */
  protected int tileWidth = 0;

  /**
   * The height of each tile if tiling has been set, or 0 otherwise.
   * The initial value is <code>0</code>.
   *
   * <p> Subclasses that do not support tiling may ignore this
   * value.
   */
  protected int tileHeight = 0;

  /**
   * A <code>boolean</code> that is <code>true</code> if this
   * <code>ImageWriteParam</code> allows tiling grid offset
   * parameters to be set.  By default, the value is
   * <code>false</code>.  Subclasses must set the value manually.
   *
   * <p> Subclasses that do not support writing tiles, or that
   * support writing but not offsetting tiles must ensure that this
   * value is set to <code>false</code>.
   */
  protected boolean canOffsetTiles = false;

  /**
   * The amount by which the tile grid origin should be offset
   * horizontally from the image origin if tiling has been set,
   * or 0 otherwise.  The initial value is <code>0</code>.
   *
   * <p> Subclasses that do not support offsetting tiles may ignore
   * this value.
   */
  protected int tileGridXOffset = 0;

  /**
   * The amount by which the tile grid origin should be offset
   * vertically from the image origin if tiling has been set,
   * or 0 otherwise.  The initial value is <code>0</code>.
   *
   * <p> Subclasses that do not support offsetting tiles may ignore
   * this value.
   */
  protected int tileGridYOffset = 0;

  /**
   * A <code>boolean</code> that is <code>true</code> if this
   * <code>ImageWriteParam</code> allows images to be written as a
   * progressive sequence of increasing quality passes.  By default,
   * the value is <code>false</code>.  Subclasses must set the value
   * manually.
   *
   * <p> Subclasses that do not support progressive encoding must
   * ensure that this value is set to <code>false</code>.
   */
  protected boolean canWriteProgressive = false;

  /**
   * The mode controlling progressive encoding, which must be set to
   * one of the four <code>MODE_*</code> values, except
   * <code>MODE_EXPLICIT</code>.  The default is
   * <code>MODE_COPY_FROM_METADATA</code>.
   *
   * <p> Subclasses that do not support progressive encoding may
   * ignore this value.
   *
   * @see #MODE_DISABLED
   * @see #MODE_EXPLICIT
   * @see #MODE_COPY_FROM_METADATA
   * @see #MODE_DEFAULT
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   */
  protected int progressiveMode = MODE_COPY_FROM_METADATA;

  /**
   * A <code>boolean</code> that is <code>true</code> if this writer
   * can write images using compression. By default, the value is
   * <code>false</code>.  Subclasses must set the value manually.
   *
   * <p> Subclasses that do not support compression must ensure that
   * this value is set to <code>false</code>.
   */
  protected boolean canWriteCompressed = false;

  /**
   * The mode controlling compression settings, which must be set to
   * one of the four <code>MODE_*</code> values.  The default is
   * <code>MODE_COPY_FROM_METADATA</code>.
   *
   * <p> Subclasses that do not support compression may ignore this
   * value.
   *
   * @see #MODE_DISABLED
   * @see #MODE_EXPLICIT
   * @see #MODE_COPY_FROM_METADATA
   * @see #MODE_DEFAULT
   * @see #setCompressionMode
   * @see #getCompressionMode
   */
  protected int compressionMode = MODE_COPY_FROM_METADATA;

  /**
   * An array of <code>String</code>s containing the names of the
   * available compression types.  Subclasses must set the value
   * manually.
   *
   * <p> Subclasses that do not support compression may ignore this
   * value.
   */
  protected String[] compressionTypes = null;

  /**
   * A <code>String</code> containing the name of the current
   * compression type, or <code>null</code> if none is set.
   *
   * <p> Subclasses that do not support compression may ignore this
   * value.
   */
  protected String compressionType = null;

  /**
   * A <code>float</code> containing the current compression quality
   * setting.  The initial value is <code>1.0F</code>.
   *
   * <p> Subclasses that do not support compression may ignore this
   * value.
   */
  protected float compressionQuality = 1.0F;

  /**
   * A <code>Locale</code> to be used to localize compression type
   * names and quality descriptions, or <code>null</code> to use a
   * default <code>Locale</code>.  Subclasses must set the value
   * manually.
   */
  protected Locale locale = null;

  /**
   * Constructs an empty <code>ImageWriteParam</code>.  It is up to
   * the subclass to set up the instance variables properly.
   */
  protected ImageWriteParam() {
  }

  /**
   * Constructs an <code>ImageWriteParam</code> set to use a
   * given <code>Locale</code>.
   *
   * @param locale a <code>Locale</code> to be used to localize compression type names and quality
   * descriptions, or <code>null</code>.
   */
  public ImageWriteParam(Locale locale) {
    this.locale = locale;
  }

  // Return a deep copy of the array
  private static Dimension[] clonePreferredTileSizes(Dimension[] sizes) {
    if (sizes == null) {
      return null;
    }
    Dimension[] temp = new Dimension[sizes.length];
    for (int i = 0; i < sizes.length; i++) {
      temp[i] = new Dimension(sizes[i]);
    }
    return temp;
  }

  /**
   * Returns the currently set <code>Locale</code>, or
   * <code>null</code> if only a default <code>Locale</code> is
   * supported.
   *
   * @return the current <code>Locale</code>, or <code>null</code>.
   */
  public Locale getLocale() {
    return locale;
  }

  /**
   * Returns <code>true</code> if the writer can perform tiling
   * while writing.  If this method returns <code>false</code>, then
   * <code>setTiling</code> will throw an
   * <code>UnsupportedOperationException</code>.
   *
   * @return <code>true</code> if the writer supports tiling.
   * @see #canOffsetTiles()
   * @see #setTiling(int, int, int, int)
   */
  public boolean canWriteTiles() {
    return canWriteTiles;
  }

  /**
   * Returns <code>true</code> if the writer can perform tiling with
   * non-zero grid offsets while writing.  If this method returns
   * <code>false</code>, then <code>setTiling</code> will throw an
   * <code>UnsupportedOperationException</code> if the grid offset
   * arguments are not both zero.  If <code>canWriteTiles</code>
   * returns <code>false</code>, this method will return
   * <code>false</code> as well.
   *
   * @return <code>true</code> if the writer supports non-zero tile offsets.
   * @see #canWriteTiles()
   * @see #setTiling(int, int, int, int)
   */
  public boolean canOffsetTiles() {
    return canOffsetTiles;
  }

  /**
   * Determines whether the image will be tiled in the output
   * stream and, if it will, how the tiling parameters will be
   * determined.  The modes are interpreted as follows:
   *
   * <ul>
   *
   * <li><code>MODE_DISABLED</code> - The image will not be tiled.
   * <code>setTiling</code> will throw an
   * <code>IllegalStateException</code>.
   *
   * <li><code>MODE_DEFAULT</code> - The image will be tiled using
   * default parameters.  <code>setTiling</code> will throw an
   * <code>IllegalStateException</code>.
   *
   * <li><code>MODE_EXPLICIT</code> - The image will be tiled
   * according to parameters given in the {@link #setTiling setTiling}
   * method.  Any previously set tiling parameters are discarded.
   *
   * <li><code>MODE_COPY_FROM_METADATA</code> - The image will
   * conform to the metadata object passed in to a write.
   * <code>setTiling</code> will throw an
   * <code>IllegalStateException</code>.
   *
   * </ul>
   *
   * @param mode The mode to use for tiling.
   * @throws UnsupportedOperationException if <code>canWriteTiles</code> returns
   * <code>false</code>.
   * @throws IllegalArgumentException if <code>mode</code> is not one of the modes listed above.
   * @see #setTiling
   * @see #getTilingMode
   */
  public void setTilingMode(int mode) {
    if (canWriteTiles() == false) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (mode < MODE_DISABLED || mode > MAX_MODE) {
      throw new IllegalArgumentException("Illegal value for mode!");
    }
    this.tilingMode = mode;
    if (mode == MODE_EXPLICIT) {
      unsetTiling();
    }
  }

  /**
   * Returns the current tiling mode, if tiling is supported.
   * Otherwise throws an <code>UnsupportedOperationException</code>.
   *
   * @return the current tiling mode.
   * @throws UnsupportedOperationException if <code>canWriteTiles</code> returns
   * <code>false</code>.
   * @see #setTilingMode
   */
  public int getTilingMode() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported");
    }
    return tilingMode;
  }

  /**
   * Returns an array of <code>Dimension</code>s indicating the
   * legal size ranges for tiles as they will be encoded in the
   * output file or stream.  The returned array is a copy.
   *
   * <p> The information is returned as a set of pairs; the first
   * element of a pair contains an (inclusive) minimum width and
   * height, and the second element contains an (inclusive) maximum
   * width and height.  Together, each pair defines a valid range of
   * sizes.  To specify a fixed size, use the same width and height
   * for both elements.  To specify an arbitrary range, a value of
   * <code>null</code> is used in place of an actual array of
   * <code>Dimension</code>s.
   *
   * <p> If no array is specified on the constructor, but tiling is
   * allowed, then this method returns <code>null</code>.
   *
   * @return an array of <code>Dimension</code>s with an even length of at least two, or
   * <code>null</code>.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   */
  public Dimension[] getPreferredTileSizes() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported");
    }
    return clonePreferredTileSizes(preferredTileSizes);
  }

  /**
   * Specifies that the image should be tiled in the output stream.
   * The <code>tileWidth</code> and <code>tileHeight</code>
   * parameters specify the width and height of the tiles in the
   * file.  If the tile width or height is greater than the width or
   * height of the image, the image is not tiled in that dimension.
   *
   * <p> If <code>canOffsetTiles</code> returns <code>false</code>,
   * then the <code>tileGridXOffset</code> and
   * <code>tileGridYOffset</code> parameters must be zero.
   *
   * @param tileWidth the width of each tile.
   * @param tileHeight the height of each tile.
   * @param tileGridXOffset the horizontal offset of the tile grid.
   * @param tileGridYOffset the vertical offset of the tile grid.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @throws UnsupportedOperationException if the plug-in does not support grid offsets, and the
   * grid offsets are not both zero.
   * @throws IllegalArgumentException if the tile size is not within one of the allowable ranges
   * returned by <code>getPreferredTileSizes</code>.
   * @throws IllegalArgumentException if <code>tileWidth</code> or <code>tileHeight</code> is less
   * than or equal to 0.
   * @see #canWriteTiles
   * @see #canOffsetTiles
   * @see #getTileWidth()
   * @see #getTileHeight()
   * @see #getTileGridXOffset()
   * @see #getTileGridYOffset()
   */
  public void setTiling(int tileWidth,
      int tileHeight,
      int tileGridXOffset,
      int tileGridYOffset) {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    if (tileWidth <= 0 || tileHeight <= 0) {
      throw new IllegalArgumentException
          ("tile dimensions are non-positive!");
    }
    boolean tilesOffset = (tileGridXOffset != 0) || (tileGridYOffset != 0);
    if (!canOffsetTiles() && tilesOffset) {
      throw new UnsupportedOperationException("Can't offset tiles!");
    }
    if (preferredTileSizes != null) {
      boolean ok = true;
      for (int i = 0; i < preferredTileSizes.length; i += 2) {
        Dimension min = preferredTileSizes[i];
        Dimension max = preferredTileSizes[i + 1];
        if ((tileWidth < min.width) ||
            (tileWidth > max.width) ||
            (tileHeight < min.height) ||
            (tileHeight > max.height)) {
          ok = false;
          break;
        }
      }
      if (!ok) {
        throw new IllegalArgumentException("Illegal tile size!");
      }
    }

    this.tilingSet = true;
    this.tileWidth = tileWidth;
    this.tileHeight = tileHeight;
    this.tileGridXOffset = tileGridXOffset;
    this.tileGridYOffset = tileGridYOffset;
  }

  /**
   * Removes any previous tile grid parameters specified by calls to
   * <code>setTiling</code>.
   *
   * <p> The default implementation sets the instance variables
   * <code>tileWidth</code>, <code>tileHeight</code>,
   * <code>tileGridXOffset</code>, and
   * <code>tileGridYOffset</code> to <code>0</code>.
   *
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @see #setTiling(int, int, int, int)
   */
  public void unsetTiling() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    this.tilingSet = false;
    this.tileWidth = 0;
    this.tileHeight = 0;
    this.tileGridXOffset = 0;
    this.tileGridYOffset = 0;
  }

  /**
   * Returns the width of each tile in an image as it will be
   * written to the output stream.  If tiling parameters have not
   * been set, an <code>IllegalStateException</code> is thrown.
   *
   * @return the tile width to be used for encoding.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the tiling parameters have not been set.
   * @see #setTiling(int, int, int, int)
   * @see #getTileHeight()
   */
  public int getTileWidth() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    if (!tilingSet) {
      throw new IllegalStateException("Tiling parameters not set!");
    }
    return tileWidth;
  }

  /**
   * Returns the height of each tile in an image as it will be written to
   * the output stream.  If tiling parameters have not
   * been set, an <code>IllegalStateException</code> is thrown.
   *
   * @return the tile height to be used for encoding.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the tiling parameters have not been set.
   * @see #setTiling(int, int, int, int)
   * @see #getTileWidth()
   */
  public int getTileHeight() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    if (!tilingSet) {
      throw new IllegalStateException("Tiling parameters not set!");
    }
    return tileHeight;
  }

  /**
   * Returns the horizontal tile grid offset of an image as it will
   * be written to the output stream.  If tiling parameters have not
   * been set, an <code>IllegalStateException</code> is thrown.
   *
   * @return the tile grid X offset to be used for encoding.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the tiling parameters have not been set.
   * @see #setTiling(int, int, int, int)
   * @see #getTileGridYOffset()
   */
  public int getTileGridXOffset() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    if (!tilingSet) {
      throw new IllegalStateException("Tiling parameters not set!");
    }
    return tileGridXOffset;
  }

  /**
   * Returns the vertical tile grid offset of an image as it will
   * be written to the output stream.  If tiling parameters have not
   * been set, an <code>IllegalStateException</code> is thrown.
   *
   * @return the tile grid Y offset to be used for encoding.
   * @throws UnsupportedOperationException if the plug-in does not support tiling.
   * @throws IllegalStateException if the tiling mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the tiling parameters have not been set.
   * @see #setTiling(int, int, int, int)
   * @see #getTileGridXOffset()
   */
  public int getTileGridYOffset() {
    if (!canWriteTiles()) {
      throw new UnsupportedOperationException("Tiling not supported!");
    }
    if (getTilingMode() != MODE_EXPLICIT) {
      throw new IllegalStateException("Tiling mode not MODE_EXPLICIT!");
    }
    if (!tilingSet) {
      throw new IllegalStateException("Tiling parameters not set!");
    }
    return tileGridYOffset;
  }

  /**
   * Returns <code>true</code> if the writer can write out images
   * as a series of passes of progressively increasing quality.
   *
   * @return <code>true</code> if the writer supports progressive encoding.
   * @see #setProgressiveMode
   * @see #getProgressiveMode
   */
  public boolean canWriteProgressive() {
    return canWriteProgressive;
  }

  /**
   * Specifies that the writer is to write the image out in a
   * progressive mode such that the stream will contain a series of
   * scans of increasing quality.  If progressive encoding is not
   * supported, an <code>UnsupportedOperationException</code> will
   * be thrown.
   *
   * <p>  The mode argument determines how
   * the progression parameters are chosen, and must be either
   * <code>MODE_DISABLED</code>,
   * <code>MODE_COPY_FROM_METADATA</code>, or
   * <code>MODE_DEFAULT</code>.  Otherwise an
   * <code>IllegalArgumentException</code> is thrown.
   *
   * <p> The modes are interpreted as follows:
   *
   * <ul>
   * <li><code>MODE_DISABLED</code> - No progression.  Use this to
   * turn off progression.
   *
   * <li><code>MODE_COPY_FROM_METADATA</code> - The output image
   * will use whatever progression parameters are found in the
   * metadata objects passed into the writer.
   *
   * <li><code>MODE_DEFAULT</code> - The image will be written
   * progressively, with parameters chosen by the writer.
   * </ul>
   *
   * <p> The default is <code>MODE_COPY_FROM_METADATA</code>.
   *
   * @param mode The mode for setting progression in the output stream.
   * @throws UnsupportedOperationException if the writer does not support progressive encoding.
   * @throws IllegalArgumentException if <code>mode</code> is not one of the modes listed above.
   * @see #getProgressiveMode
   */
  public void setProgressiveMode(int mode) {
    if (!canWriteProgressive()) {
      throw new UnsupportedOperationException(
          "Progressive output not supported");
    }
    if (mode < MODE_DISABLED || mode > MAX_MODE) {
      throw new IllegalArgumentException("Illegal value for mode!");
    }
    if (mode == MODE_EXPLICIT) {
      throw new IllegalArgumentException(
          "MODE_EXPLICIT not supported for progressive output");
    }
    this.progressiveMode = mode;
  }

  /**
   * Returns the current mode for writing the stream in a
   * progressive manner.
   *
   * @return the current mode for progressive encoding.
   * @throws UnsupportedOperationException if the writer does not support progressive encoding.
   * @see #setProgressiveMode
   */
  public int getProgressiveMode() {
    if (!canWriteProgressive()) {
      throw new UnsupportedOperationException
          ("Progressive output not supported");
    }
    return progressiveMode;
  }

  /**
   * Returns <code>true</code> if this writer supports compression.
   *
   * @return <code>true</code> if the writer supports compression.
   */
  public boolean canWriteCompressed() {
    return canWriteCompressed;
  }

  /**
   * Specifies whether compression is to be performed, and if so how
   * compression parameters are to be determined.  The <code>mode</code>
   * argument must be one of the four modes, interpreted as follows:
   *
   * <ul>
   * <li><code>MODE_DISABLED</code> - If the mode is set to
   * <code>MODE_DISABLED</code>, methods that query or modify the
   * compression type or parameters will throw an
   * <code>IllegalStateException</code> (if compression is
   * normally supported by the plug-in). Some writers, such as JPEG,
   * do not normally offer uncompressed output. In this case, attempting
   * to set the mode to <code>MODE_DISABLED</code> will throw an
   * <code>UnsupportedOperationException</code> and the mode will not be
   * changed.
   *
   * <li><code>MODE_EXPLICIT</code> - Compress using the
   * compression type and quality settings specified in this
   * <code>ImageWriteParam</code>.  Any previously set compression
   * parameters are discarded.
   *
   * <li><code>MODE_COPY_FROM_METADATA</code> - Use whatever
   * compression parameters are specified in metadata objects
   * passed in to the writer.
   *
   * <li><code>MODE_DEFAULT</code> - Use default compression
   * parameters.
   * </ul>
   *
   * <p> The default is <code>MODE_COPY_FROM_METADATA</code>.
   *
   * @param mode The mode for setting compression in the output stream.
   * @throws UnsupportedOperationException if the writer does not support compression, or does not
   * support the requested mode.
   * @throws IllegalArgumentException if <code>mode</code> is not one of the modes listed above.
   * @see #getCompressionMode
   */
  public void setCompressionMode(int mode) {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (mode < MODE_DISABLED || mode > MAX_MODE) {
      throw new IllegalArgumentException("Illegal value for mode!");
    }
    this.compressionMode = mode;
    if (mode == MODE_EXPLICIT) {
      unsetCompression();
    }
  }

  /**
   * Returns the current compression mode, if compression is
   * supported.
   *
   * @return the current compression mode.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @see #setCompressionMode
   */
  public int getCompressionMode() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    return compressionMode;
  }

  /**
   * Returns a list of available compression types, as an array or
   * <code>String</code>s, or <code>null</code> if a compression
   * type may not be chosen using these interfaces.  The array
   * returned is a copy.
   *
   * <p> If the writer only offers a single, mandatory form of
   * compression, it is not necessary to provide any named
   * compression types.  Named compression types should only be
   * used where the user is able to make a meaningful choice
   * between different schemes.
   *
   * <p> The default implementation checks if compression is
   * supported and throws an
   * <code>UnsupportedOperationException</code> if not.  Otherwise,
   * it returns a clone of the <code>compressionTypes</code>
   * instance variable if it is non-<code>null</code>, or else
   * returns <code>null</code>.
   *
   * @return an array of <code>String</code>s containing the (non-localized) names of available
   * compression types, or <code>null</code>.
   * @throws UnsupportedOperationException if the writer does not support compression.
   */
  public String[] getCompressionTypes() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported");
    }
    if (compressionTypes == null) {
      return null;
    }
    return (String[]) compressionTypes.clone();
  }

  /**
   * Sets the compression type to one of the values indicated by
   * <code>getCompressionTypes</code>.  If a value of
   * <code>null</code> is passed in, any previous setting is
   * removed.
   *
   * <p> The default implementation checks whether compression is
   * supported and the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, it calls
   * <code>getCompressionTypes</code> and checks if
   * <code>compressionType</code> is one of the legal values.  If it
   * is, the <code>compressionType</code> instance variable is set.
   * If <code>compressionType</code> is <code>null</code>, the
   * instance variable is set without performing any checking.
   *
   * @param compressionType one of the <code>String</code>s returned by
   * <code>getCompressionTypes</code>, or <code>null</code> to remove any previous setting.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws UnsupportedOperationException if there are no settable compression types.
   * @throws IllegalArgumentException if <code>compressionType</code> is non-<code>null</code> but
   * is not one of the values returned by <code>getCompressionTypes</code>.
   * @see #getCompressionTypes
   * @see #getCompressionType
   * @see #unsetCompression
   */
  public void setCompressionType(String compressionType) {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    String[] legalTypes = getCompressionTypes();
    if (legalTypes == null) {
      throw new UnsupportedOperationException(
          "No settable compression types");
    }
    if (compressionType != null) {
      boolean found = false;
      if (legalTypes != null) {
        for (int i = 0; i < legalTypes.length; i++) {
          if (compressionType.equals(legalTypes[i])) {
            found = true;
            break;
          }
        }
      }
      if (!found) {
        throw new IllegalArgumentException("Unknown compression type!");
      }
    }
    this.compressionType = compressionType;
  }

  /**
   * Returns the currently set compression type, or
   * <code>null</code> if none has been set.  The type is returned
   * as a <code>String</code> from among those returned by
   * <code>getCompressionTypes</code>.
   * If no compression type has been set, <code>null</code> is
   * returned.
   *
   * <p> The default implementation checks whether compression is
   * supported and the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, it returns the value of the
   * <code>compressionType</code> instance variable.
   *
   * @return the current compression type as a <code>String</code>, or <code>null</code> if no type
   * is set.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @see #setCompressionType
   */
  public String getCompressionType() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    return compressionType;
  }

  /**
   * Removes any previous compression type and quality settings.
   *
   * <p> The default implementation sets the instance variable
   * <code>compressionType</code> to <code>null</code>, and the
   * instance variable <code>compressionQuality</code> to
   * <code>1.0F</code>.
   *
   * @throws UnsupportedOperationException if the plug-in does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @see #setCompressionType
   * @see #setCompressionQuality
   */
  public void unsetCompression() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    this.compressionType = null;
    this.compressionQuality = 1.0F;
  }

  /**
   * Returns a localized version of the name of the current
   * compression type, using the <code>Locale</code> returned by
   * <code>getLocale</code>.
   *
   * <p> The default implementation checks whether compression is
   * supported and the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>compressionType</code> is <code>non-null</code> the value
   * of <code>getCompressionType</code> is returned as a
   * convenience.
   *
   * @return a <code>String</code> containing a localized version of the name of the current
   * compression type.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if no compression type is set.
   */
  public String getLocalizedCompressionTypeName() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if (getCompressionType() == null) {
      throw new IllegalStateException("No compression type set!");
    }
    return getCompressionType();
  }

  /**
   * Returns <code>true</code> if the current compression type
   * provides lossless compression.  If a plug-in provides only
   * one mandatory compression type, then this method may be
   * called without calling <code>setCompressionType</code> first.
   *
   * <p> If there are multiple compression types but none has
   * been set, an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks whether compression is
   * supported and the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> is <code>null</code> or
   * <code>getCompressionType()</code> is non-<code>null</code>
   * <code>true</code> is returned as a convenience.
   *
   * @return <code>true</code> if the current compression type is lossless.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   */
  public boolean isCompressionLossless() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if ((getCompressionTypes() != null) &&
        (getCompressionType() == null)) {
      throw new IllegalStateException("No compression type set!");
    }
    return true;
  }

  /**
   * Sets the compression quality to a value between <code>0</code>
   * and <code>1</code>.  Only a single compression quality setting
   * is supported by default; writers can provide extended versions
   * of <code>ImageWriteParam</code> that offer more control.  For
   * lossy compression schemes, the compression quality should
   * control the tradeoff between file size and image quality (for
   * example, by choosing quantization tables when writing JPEG
   * images).  For lossless schemes, the compression quality may be
   * used to control the tradeoff between file size and time taken
   * to perform the compression (for example, by optimizing row
   * filters and setting the ZLIB compression level when writing
   * PNG images).
   *
   * <p> A compression quality setting of 0.0 is most generically
   * interpreted as "high compression is important," while a setting of
   * 1.0 is most generically interpreted as "high image quality is
   * important."
   *
   * <p> If there are multiple compression types but none has been
   * set, an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks that compression is
   * supported, and that the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> returns <code>null</code> or
   * <code>compressionType</code> is non-<code>null</code> it sets
   * the <code>compressionQuality</code> instance variable.
   *
   * @param quality a <code>float</code> between <code>0</code>and <code>1</code> indicating the
   * desired quality level.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   * @throws IllegalArgumentException if <code>quality</code> is not between <code>0</code>and
   * <code>1</code>, inclusive.
   * @see #getCompressionQuality
   */
  public void setCompressionQuality(float quality) {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if (getCompressionTypes() != null && getCompressionType() == null) {
      throw new IllegalStateException("No compression type set!");
    }
    if (quality < 0.0F || quality > 1.0F) {
      throw new IllegalArgumentException("Quality out-of-bounds!");
    }
    this.compressionQuality = quality;
  }

  /**
   * Returns the current compression quality setting.
   *
   * <p> If there are multiple compression types but none has been
   * set, an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks that compression is
   * supported and that the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> is <code>null</code> or
   * <code>getCompressionType()</code> is non-<code>null</code>, it
   * returns the value of the <code>compressionQuality</code>
   * instance variable.
   *
   * @return the current compression quality setting.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   * @see #setCompressionQuality
   */
  public float getCompressionQuality() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if ((getCompressionTypes() != null) &&
        (getCompressionType() == null)) {
      throw new IllegalStateException("No compression type set!");
    }
    return compressionQuality;
  }


  /**
   * Returns a <code>float</code> indicating an estimate of the
   * number of bits of output data for each bit of input image data
   * at the given quality level.  The value will typically lie
   * between <code>0</code> and <code>1</code>, with smaller values
   * indicating more compression.  A special value of
   * <code>-1.0F</code> is used to indicate that no estimate is
   * available.
   *
   * <p> If there are multiple compression types but none has been set,
   * an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks that compression is
   * supported and the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> is <code>null</code> or
   * <code>getCompressionType()</code> is non-<code>null</code>, and
   * <code>quality</code> is within bounds, it returns
   * <code>-1.0</code>.
   *
   * @param quality the quality setting whose bit rate is to be queried.
   * @return an estimate of the compressed bit rate, or <code>-1.0F</code> if no estimate is
   * available.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   * @throws IllegalArgumentException if <code>quality</code> is not between <code>0</code>and
   * <code>1</code>, inclusive.
   */
  public float getBitRate(float quality) {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if ((getCompressionTypes() != null) &&
        (getCompressionType() == null)) {
      throw new IllegalStateException("No compression type set!");
    }
    if (quality < 0.0F || quality > 1.0F) {
      throw new IllegalArgumentException("Quality out-of-bounds!");
    }
    return -1.0F;
  }

  /**
   * Returns an array of <code>String</code>s that may be used along
   * with <code>getCompressionQualityValues</code> as part of a user
   * interface for setting or displaying the compression quality
   * level.  The <code>String</code> with index <code>i</code>
   * provides a description of the range of quality levels between
   * <code>getCompressionQualityValues[i]</code> and
   * <code>getCompressionQualityValues[i + 1]</code>.  Note that the
   * length of the array returned from
   * <code>getCompressionQualityValues</code> will always be one
   * greater than that returned from
   * <code>getCompressionQualityDescriptions</code>.
   *
   * <p> As an example, the strings "Good", "Better", and "Best"
   * could be associated with the ranges <code>[0, .33)</code>,
   * <code>[.33, .66)</code>, and <code>[.66, 1.0]</code>.  In this
   * case, <code>getCompressionQualityDescriptions</code> would
   * return <code>{ "Good", "Better", "Best" }</code> and
   * <code>getCompressionQualityValues</code> would return
   * <code>{ 0.0F, .33F, .66F, 1.0F }</code>.
   *
   * <p> If no descriptions are available, <code>null</code> is
   * returned.  If <code>null</code> is returned from
   * <code>getCompressionQualityValues</code>, this method must also
   * return <code>null</code>.
   *
   * <p> The descriptions should be localized for the
   * <code>Locale</code> returned by <code>getLocale</code>, if it
   * is non-<code>null</code>.
   *
   * <p> If there are multiple compression types but none has been set,
   * an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks that compression is
   * supported and that the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> is <code>null</code> or
   * <code>getCompressionType()</code> is non-<code>null</code>, it
   * returns <code>null</code>.
   *
   * @return an array of <code>String</code>s containing localized descriptions of the compression
   * quality levels.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   * @see #getCompressionQualityValues
   */
  public String[] getCompressionQualityDescriptions() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if ((getCompressionTypes() != null) &&
        (getCompressionType() == null)) {
      throw new IllegalStateException("No compression type set!");
    }
    return null;
  }

  /**
   * Returns an array of <code>float</code>s that may be used along
   * with <code>getCompressionQualityDescriptions</code> as part of a user
   * interface for setting or displaying the compression quality
   * level.  See {@link #getCompressionQualityDescriptions
   * getCompressionQualityDescriptions} for more information.
   *
   * <p> If no descriptions are available, <code>null</code> is
   * returned.  If <code>null</code> is returned from
   * <code>getCompressionQualityDescriptions</code>, this method
   * must also return <code>null</code>.
   *
   * <p> If there are multiple compression types but none has been set,
   * an <code>IllegalStateException</code> is thrown.
   *
   * <p> The default implementation checks that compression is
   * supported and that the compression mode is
   * <code>MODE_EXPLICIT</code>.  If so, if
   * <code>getCompressionTypes()</code> is <code>null</code> or
   * <code>getCompressionType()</code> is non-<code>null</code>, it
   * returns <code>null</code>.
   *
   * @return an array of <code>float</code>s indicating the boundaries between the compression
   * quality levels as described by the <code>String</code>s from <code>getCompressionQualityDescriptions</code>.
   * @throws UnsupportedOperationException if the writer does not support compression.
   * @throws IllegalStateException if the compression mode is not <code>MODE_EXPLICIT</code>.
   * @throws IllegalStateException if the set of legal compression types is non-<code>null</code>
   * and the current compression type is <code>null</code>.
   * @see #getCompressionQualityDescriptions
   */
  public float[] getCompressionQualityValues() {
    if (!canWriteCompressed()) {
      throw new UnsupportedOperationException(
          "Compression not supported.");
    }
    if (getCompressionMode() != MODE_EXPLICIT) {
      throw new IllegalStateException
          ("Compression mode not MODE_EXPLICIT!");
    }
    if ((getCompressionTypes() != null) &&
        (getCompressionType() == null)) {
      throw new IllegalStateException("No compression type set!");
    }
    return null;
  }
}
